﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using Filtry.Utilities;

namespace Filtry.Data
{
    public class GrayscaleImage
    {
        private const PixelFormat ImagePixelFormat = PixelFormat.Format24bppRgb;
        private static int _stride;
        private byte[] _bytes;

        public GrayscaleImage(Image img)
        {
            Bitmap bmpGray = MakeGrayscale(img);
            _bytes = GetByteArrayFromBitmap(bmpGray);
            Height = img.Height;
            Width = img.Width;
            img.Dispose();
            bmpGray.Dispose();
        }

        public int Height { get; set; }
        public int Width { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="indX"></param>
        /// <param name="indY"></param>
        /// <returns></returns>
        public byte this[int indX, int indY]
        {
            get { return _bytes[indX + indY * Width]; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bmp"></param>
        /// <returns></returns>
        private static byte[] GetByteArrayFromBitmap(Bitmap bmp)
        {
            var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);

            IntPtr ptr = bitmapData.Scan0;
            int length = bitmapData.Stride * bmp.Height;
            var rgbValues = new byte[length]; // gray
            var bytes = new byte[bitmapData.Height * bitmapData.Width];
            _stride = bitmapData.Stride;

            Marshal.Copy(ptr, rgbValues, 0, length);

            for (int i = 0; i < bitmapData.Height; i++)
            {
                for (int j = 0; j < bitmapData.Width; j++)
                {
                    bytes[i * bitmapData.Width + j] = rgbValues[(i * bitmapData.Stride) + (j * 3)];
                }
            }
            bmp.UnlockBits(bitmapData);
            return bytes;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public byte[] GetAllPixels()
        {
            return _bytes;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pixels"></param>
        public void SetAllPixels(byte[] pixels)
        {
            _bytes = pixels;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="indX"></param>
        /// <param name="indY"></param>
        /// <returns></returns>
        public byte GetPixel(int indX, int indY)
        {
            return _bytes[indX + indY * Width];
        }

        /// <summary>
        ///     Transforms original image to grayscale bitmap.
        /// </summary>
        /// <param name="original"></param>
        /// <returns></returns>
        public static Bitmap MakeGrayscale(Image original)
        {
            //create a blank bitmap the same size as original
            var newBitmap = new Bitmap(original.Width, original.Height, ImagePixelFormat);

            //get a graphics object from the new image
            Graphics g = Graphics.FromImage(newBitmap);

            //create the grayscale ColorMatrix
            var colorMatrix = new ColorMatrix(
                new[]
                    {
                        new[] {.3f, .3f, .3f, 0, 0},
                        new[] {.59f, .59f, .59f, 0, 0},
                        new[] {.11f, .11f, .11f, 0, 0},
                        new[] {0f, 0, 0, 1, 0},
                        new[] {0f, 0, 0, 0, 1}
                    });

            //create some image attributes
            var attributes = new ImageAttributes();

            //set the color matrix attribute
            attributes.SetColorMatrix(colorMatrix);

            //draw the original image on the new image
            //using the grayscale color matrix
            g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                        0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);

            //dispose the Graphics object
            g.Dispose();
            return newBitmap;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Bitmap GetBitmap()
        {
            var bytes = new byte[_stride * Height];
            for (int i = 0; i < Height; i++)
            {
                for (int j = 0; j < Width; j++)
                {
                    byte tmp = _bytes[i * Width + j];
                    bytes[(i * _stride) + (j * 3)] = tmp;
                    bytes[(i * _stride) + (j * 3) + 1] = tmp;
                    bytes[(i * _stride) + (j * 3) + 2] = tmp;
                }
            }

            var bitmap = new Bitmap(Width, Height);
            BitmapData data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.WriteOnly,
                                              ImagePixelFormat);
            Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
            bitmap.UnlockBits(data);
            return bitmap;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public GrayscaleImage Clone()
        {
            var clonedImage = (GrayscaleImage)MemberwiseClone();
            clonedImage._bytes = (byte[])_bytes.Clone();
            return clonedImage;
        }

        public double CalculateVariance()
        {
            double mean = CalculateMean();
            if (Math.Abs(mean - 0.0) < 0.0001)
            {
                return double.NaN;
            }
            var d = Tools.CalculateDistance(_bytes, mean, Metric.Manhattan, ImgType.Gray).Sum(b => b * b);
            return d / _bytes.Length;

        }

        private double CalculateMean()
        {
            if (_bytes.Length == 0)
            {
                return 0;
            }
            var sum = _bytes.Sum(b => b);
            return (double)sum / _bytes.Length;
        }
    }
}